import re
import os
import sys
import smtplib
import subprocess

from urllib import splitport
from Tkinter import Tk, Frame
from Tkinter import X, Y, BOTTOM, LEFT, TOP, RIGHT, END, ACTIVE, DISABLED
from Tkinter import StringVar, Entry, Text, Label, Button
from tkMessageBox import showerror, showinfo

EMAIL_RE = re.compile(r"^(\w&.%#$&'\*+-/=?^_`{}|~]+!)*[\w&.%#$&'\*+-/=?^_`{}|~]+@(([0-9a-z]([0-9a-z-]*[0-9a-z])?\.)+[a-z]{2,6}|([0-9]{1,3}\.){3}[0-9]{1,3})$", re.IGNORECASE)

def valid_email(address):
    if not address:
        return False
    # sub is an empty string if the address is valid
    sub = EMAIL_RE.sub('', address)
    if sub == '':
        return True
    return False

def error(msg):
    showerror('Error', msg)

def info(msg):
    showinfo('Info', msg)

class ValidatingEntry(Entry):
    # base class for validating entry widgets

    def __init__(self, master, value="", **kw):
        apply(Entry.__init__, (self, master), kw)
        self.value = value
        self.variable = StringVar()
        self.variable.set(value)
        self.variable.trace("w", self.callback)
        self.config(textvariable=self.variable)
        self.callback()

    def callback(self, *dummy):
        value = self.variable.get()
        newvalue = self.validate(value)
        if newvalue is None:
            self.variable.set(self.value)
        elif newvalue != value:
            self.value = newvalue
            self.variable.set(self.newvalue)
        else:
            self.value = value

    def validate(self, value):
        # override: return value, new value, or None if invalid
        return value

    def get(self):
        return self.variable.get()

    def set(self, value):
        return self.variable.set(value)

class EmailEntry(ValidatingEntry):

    def callback(self, *dummy):
        value = self.variable.get()
        if not valid_email(value):
            self.config(bg='wheat')
        else:
            self.config(bg='white')

class NonEmptyEntry(ValidatingEntry):

    def callback(self, *dummy):
        value = self.variable.get()
        if not value:
            self.config(bg='wheat')
        else:
            self.config(bg='white')

class Widget(object):

    def focus(self):
        self.widget.focus()

class EntryWidget(Widget):

    def __init__(self, frame, label, label_width=30, 
                 entry_width=45, factory=Entry):

        self.frame = Frame(frame)

        self.label = Label(self.frame, text=label, width=label_width)
        self.label.pack(side=LEFT)

        self.widget = factory(self.frame, width=entry_width)
        self.widget.pack(side=LEFT)
        self.frame.pack(fill=X, side=TOP)

    def validate(self):
        return self.validator(self.get())

    def get(self):
        return self.widget.get()

    def set(self, value):
        self.widget.set(value)

class TextWidget(Widget):

    def __init__(self, frame, label, label_width=30, 
                 entry_width=80, entry_height=24):

        self.frame = Frame(frame)

        self.label = Label(self.frame, text=label, width=label_width)
        self.label.pack(side=LEFT)

        self.widget = Text(self.frame, width=entry_width,
                          height=entry_height)
        self.widget.pack(side=LEFT)
        self.frame.pack(fill=X, side=TOP)

    def get(self):
        return self.widget.get(1.0, END)

    def set(self, value):
        self.widget.delete(1.0, END)
        self.widget.insert(END, value)

class ButtonWidget(Widget):

    def __init__(self, frame, label, label_width=20, 
                 default=False, command=None):

        self.frame = Frame(frame)

        if command is None:
            command = lambda: True
        default = default and ACTIVE or DISABLED
        self.widget = Button(self.frame, text=label,
                             width=label_width,
                             command=command,
                             default=default)
        self.widget.pack(side=RIGHT)
        self.frame.pack(fill=X, side=TOP)

    def get(self):
        raise NotImplementedError

    def set(self, value):
        raise NotImplementedError

class MailUI(object):

    def __init__(self):
        self.root = Tk()
        self.root.title('Send Mail')

        self.frame = Frame(self.root)

        self.from_widget = EntryWidget(self.frame, label='From:', 
                                       factory=EmailEntry)
        self.to_widget = EntryWidget(self.frame, label='To:',
                                     factory=EmailEntry)
        self.subject_widget = EntryWidget(self.frame, label='Subject:',
                                          factory=NonEmptyEntry)
        self.server_widget = EntryWidget(self.frame, 
                                         label='Server (empty for localhost):')
        self.msg_widget = TextWidget(self.frame, label='Message:')
        self.ok_widget = ButtonWidget(self.frame, label='Send', 
                                      command=self.send, default=True)
        self.from_widget.focus()
        self.frame.pack(fill=X, side=BOTTOM)

    def send(self):
        from_address = self.from_widget.get()
        if not valid_email(from_address):
            error('Invalid Sender Address')
            self.from_widget.focus()
            return

        to_address = self.to_widget.get()
        if not valid_email(to_address):
            error('Invalid Destination Address')
            self.to_widget.focus()
            return

        subject = self.subject_widget.get()
        if not subject:
            error('Empty Subject')
            self.subject_widget.focus()
            return

        msg = self.msg_widget.get()
        if not msg:
            error('Empty Message Body')
            self.msg_widget.focus()
            return

        server = self.server_widget.get()

        # Add the From: and To: headers at the start!
        msg = ("From: %s\r\nTo: %s\r\nSubject: %s\r\n\r\n%s"
               % (from_address, to_address, subject, msg))

        try:
            smtp = smtplib.SMTP()
            smtp.connect(server)
        except smtplib.SMTPConnectError:
            error('Failure Connecting to Server')
            return

        smtp.set_debuglevel(1)
        
        try:
            smtp.sendmail(from_address, to_address, msg)
            info('Success!')
            self.msg_widget.set('')
            self.subject_widget.set('')
            self.to_widget.set('')
            self.to_widget.focus()
        except smtplib.SMTPSenderRefused, e:
            smtp.quit()
            msg = ['Error sending message:']
            msg.append('Sender has been refused (%s)' % e.sender)
            error('\n'.join(msg))
        except smtplib.SMTPRecipientsRefused, e:
            smtp.quit()
            msg = ['Error sending message:']
            for recipient, (error_code, error_msg) in e.recipients.items():
                msg.append('%s (%s: %s)' % (recipient, error_code, error_msg))
            error('\n'.join(msg))
        except smtplib.SMTPResponseException, e:
            smtp.quit()
            error('Error sending message (%s: %s)' % e.smtp_code, e.smtp_error)
        else:
            smtp.quit()

    def run(self):
        self.root.mainloop()

if __name__ == '__main__':
    if not '--log' in sys.argv:
        out = open('mail.log', 'wb')
        cmd = [sys.executable, 
               os.path.normpath(os.path.abspath(__file__)),
               '--log']
        sys.exit(subprocess.call(cmd, stdout=out, stderr=out))
    else:
        ui = MailUI()
        ui.run()
